<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<meta name="theme-color" content="#222">
<meta name="generator" content="Hexo 5.4.0">
  <link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png">
  <link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32-next.png">
  <link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16-next.png">
  <link rel="mask-icon" href="/images/logo.svg" color="#222">

<link rel="stylesheet" href="/css/main.css">


<link rel="stylesheet" href="/lib/font-awesome/css/all.min.css">

<script id="hexo-configurations">
    var NexT = window.NexT || {};
    var CONFIG = {"hostname":"blog.zeroable.cn","root":"/","scheme":"Pisces","version":"7.8.0","exturl":false,"sidebar":{"position":"left","display":"post","padding":18,"offset":12,"onmobile":false},"copycode":{"enable":false,"show_result":false,"style":null},"back2top":{"enable":true,"sidebar":false,"scrollpercent":false},"bookmark":{"enable":false,"color":"#222","save":"auto"},"fancybox":false,"mediumzoom":false,"lazyload":false,"pangu":false,"comments":{"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},"algolia":{"hits":{"per_page":10},"labels":{"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}},"localsearch":{"enable":false,"trigger":"auto","top_n_per_article":1,"unescape":false,"preload":false},"motion":{"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}}};
  </script>

  <meta name="description" content="微信公众号：码农小谭，一个热爱coding、生活、分享、探讨的打工人，如果我的文章对你有帮助，麻烦给个关注吧~公众号分享技术博文、生活百事、欢迎关注~资料获取方式，无任何套路，也不需要解压验证码，如下：需要java相关资料请回复【java】需要数据库相关资料请回复【数据库】需要计算机网络相关资料请回复【计算机网络】需要操作系统相关资料请回复【操作系统】需要算法相关资料请回复【算法】如果资料链接失">
<meta property="og:type" content="article">
<meta property="og:title" content="netty源码解析01-NioEventLoop详解">
<meta property="og:url" content="https://blog.zeroable.cn/2021/10/08/netty%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%9001-NioEventLoop%E8%AF%A6%E8%A7%A3/index.html">
<meta property="og:site_name" content="zeroable">
<meta property="og:description" content="微信公众号：码农小谭，一个热爱coding、生活、分享、探讨的打工人，如果我的文章对你有帮助，麻烦给个关注吧~公众号分享技术博文、生活百事、欢迎关注~资料获取方式，无任何套路，也不需要解压验证码，如下：需要java相关资料请回复【java】需要数据库相关资料请回复【数据库】需要计算机网络相关资料请回复【计算机网络】需要操作系统相关资料请回复【操作系统】需要算法相关资料请回复【算法】如果资料链接失">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://gitee.com/zeroable/ima/raw/master/img/EWM_GZH_MNXT.png">
<meta property="og:image" content="https://gitee.com/zeroable/ima/raw/master/img/image-20211028204054653.png">
<meta property="og:image" content="https://gitee.com/zeroable/ima/raw/master/img/EWM_GZH_MNXT.png">
<meta property="article:published_time" content="2021-10-08T02:17:55.000Z">
<meta property="article:modified_time" content="2021-11-18T08:02:28.346Z">
<meta property="article:author" content="zeroable">
<meta property="article:tag" content="netty">
<meta property="article:tag" content="java">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://gitee.com/zeroable/ima/raw/master/img/EWM_GZH_MNXT.png">

<link rel="canonical" href="https://blog.zeroable.cn/2021/10/08/netty%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%9001-NioEventLoop%E8%AF%A6%E8%A7%A3/">


<script id="page-configurations">
  // https://hexo.io/docs/variables.html
  CONFIG.page = {
    sidebar: "",
    isHome : false,
    isPost : true,
    lang   : 'zh-CN'
  };
</script>

  <title>netty源码解析01-NioEventLoop详解 | zeroable</title>
  






  <noscript>
  <style>
  .use-motion .brand,
  .use-motion .menu-item,
  .sidebar-inner,
  .use-motion .post-block,
  .use-motion .pagination,
  .use-motion .comments,
  .use-motion .post-header,
  .use-motion .post-body,
  .use-motion .collection-header { opacity: initial; }

  .use-motion .site-title,
  .use-motion .site-subtitle {
    opacity: initial;
    top: initial;
  }

  .use-motion .logo-line-before i { left: initial; }
  .use-motion .logo-line-after i { right: initial; }
  </style>
</noscript>

</head>

<body itemscope itemtype="http://schema.org/WebPage">
  <div class="container use-motion">
    <div class="headband"></div>

    <header class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-container">
  <div class="site-nav-toggle">
    <div class="toggle" aria-label="切换导航栏">
      <span class="toggle-line toggle-line-first"></span>
      <span class="toggle-line toggle-line-middle"></span>
      <span class="toggle-line toggle-line-last"></span>
    </div>
  </div>

  <div class="site-meta">

    <a href="/" class="brand" rel="start">
      <span class="logo-line-before"><i></i></span>
      <h1 class="site-title">zeroable</h1>
      <span class="logo-line-after"><i></i></span>
    </a>
      <p class="site-subtitle" itemprop="description">微信公众号：码农小谭</p>
  </div>

  <div class="site-nav-right">
    <div class="toggle popup-trigger">
    </div>
  </div>
</div>




<nav class="site-nav">
  <ul id="menu" class="main-menu menu">
        <li class="menu-item menu-item-home">

    <a href="/" rel="section"><i class="fa fa-home fa-fw"></i>首页</a>

  </li>
        <li class="menu-item menu-item-archives">

    <a href="/archives/" rel="section"><i class="fa fa-archive fa-fw"></i>归档</a>

  </li>
        <li class="menu-item menu-item-categories">

    <a href="/categories/" rel="section"><i class="fa fa-th fa-fw"></i>分类</a>

  </li>
        <li class="menu-item menu-item-tags">

    <a href="/tags/" rel="section"><i class="fa fa-tags fa-fw"></i>标签</a>

  </li>
        <li class="menu-item menu-item-about">

    <a href="/about/" rel="section"><i class="fa fa-user fa-fw"></i>关于</a>

  </li>
  </ul>
</nav>




</div>
    </header>

    
  <div class="back-to-top">
    <i class="fa fa-arrow-up"></i>
    <span>0%</span>
  </div>


    <main class="main">
      <div class="main-inner">
        <div class="content-wrap">
          

          <div class="content post posts-expand">
            

    
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="https://blog.zeroable.cn/2021/10/08/netty%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%9001-NioEventLoop%E8%AF%A6%E8%A7%A3/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.gif">
      <meta itemprop="name" content="zeroable">
      <meta itemprop="description" content="种一棵树最好的时间是十年前，其次是现在。">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="zeroable">
    </span>
      <header class="post-header">
        <h1 class="post-title" itemprop="name headline">
          netty源码解析01-NioEventLoop详解
        </h1>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>

              <time title="创建时间：2021-10-08 10:17:55" itemprop="dateCreated datePublished" datetime="2021-10-08T10:17:55+08:00">2021-10-08</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2021-11-18 16:02:28" itemprop="dateModified" datetime="2021-11-18T16:02:28+08:00">2021-11-18</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/java/" itemprop="url" rel="index"><span itemprop="name">java</span></a>
                </span>
                  ，
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/java/netty/" itemprop="url" rel="index"><span itemprop="name">netty</span></a>
                </span>
            </span>

          
            <span class="post-meta-item" title="阅读次数" id="busuanzi_container_page_pv" style="display: none;">
              <span class="post-meta-item-icon">
                <i class="fa fa-eye"></i>
              </span>
              <span class="post-meta-item-text">阅读次数：</span>
              <span id="busuanzi_value_page_pv"></span>
            </span>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
        <blockquote>
<p>微信公众号：码农小谭，一个热爱coding、生活、分享、探讨的打工人，如果我的文章对你有帮助，麻烦给个关注吧~<br>公众号分享技术博文、生活百事、欢迎关注~<br>资料获取方式，无任何套路，也不需要解压验证码，如下：<br>需要java相关资料请回复【java】<br>需要数据库相关资料请回复【数据库】<br>需要计算机网络相关资料请回复【计算机网络】<br>需要操作系统相关资料请回复【操作系统】<br>需要算法相关资料请回复【算法】<br>如果资料链接失效，请点击联系作者添加微信，第一时间会进行更新。</p>
</blockquote>
<p><img src="https://gitee.com/zeroable/ima/raw/master/img/EWM_GZH_MNXT.png"></p>
<blockquote>
<p>提示:如果没有听过或者使用过<code>java nio</code> 和<code>netty</code>，不建议直接阅读此文章，建议先去了解<code>java nio </code>和<code>netty</code>之后再阅读此文章。</p>
</blockquote>
<span id="more"></span>

<h1 id="netty-NioEventLoop详解"><a href="#netty-NioEventLoop详解" class="headerlink" title="netty NioEventLoop详解"></a>netty NioEventLoop详解</h1><p>上一篇文章，我们介绍了netty的启动流程，我们知道了netty在jdk nio的基础上多做了哪些事情，在上篇文章中，我们接触到了netty 的一个核心组件，就是我们的NioEventLoop。那么我们这篇文章就来对NioEventLoop做一个详细的探索吧~</p>
<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>如果在面试中遇到过面试官问到netty相关的面试题，或许以下的几个题目你不会陌生：</p>
<ol>
<li>默认情况下，netty服务端起多少个线程呢？什么时候启动呢？</li>
<li>知道jdk nio 什么时候会出现空轮询导致CPU飙升到100%吗？netty又是如何解决了这个BUG呢？</li>
<li>netty是如何保证异步串行无锁化的？</li>
</ol>
<h2 id="NioEventLoop-创建"><a href="#NioEventLoop-创建" class="headerlink" title="NioEventLoop 创建"></a>NioEventLoop 创建</h2><p>NioEventLoop的创建其实就比较简单了，它是从创建EventLoopGroup开始的，你还记得我们上一节的demo代码中，有这么一行代码：<code>NioEventLoopGroup bossGroup = new NioEventLoopGroup();</code> 那么NioEventLoop就是从<code>new NioEventLoopGroup();</code>开始的。大致的流程我还是先告诉你：</p>
<ol>
<li><code>new NioEventLoopGroup();</code> 创建线程组，默认线程数量是cpu核数*2。<ol>
<li><code>new ThreadPreTaskExecutor();</code> 创建线程创建器。</li>
<li><code>for(nThreads)&#123;newChild()&#125;</code> 创建对应数量的NioEventLoop，每个NioEventLoop对应一个线程。</li>
<li><code>chooser = chooserFactory.newChooser(children);</code>通过这个创建一个线程选择器，以后netty执行对应的事件，就通过这个选择器来选择哪一个NioEventLoop来进行执行。</li>
</ol>
</li>
</ol>
<p>我们一起去看看这个EventLoopGroup的构造函数做了哪些事情吧：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//EventLoopGroup.java</span></span><br><span class="line"><span class="comment">//第一步，从这里开始。</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">NioEventLoopGroup</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">this</span>(<span class="number">0</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//第二步，依次调用对应的构造函数。</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">NioEventLoopGroup</span><span class="params">(<span class="keyword">int</span> nThreads)</span> </span>&#123;</span><br><span class="line">    <span class="comment">//这里调用了一个新的构造函数，并且传入了一个Executor,为null，这里Executor的作用就是来创建NioEventLoop对应的底层的线程。</span></span><br><span class="line">    <span class="keyword">this</span>(nThreads, (Executor) <span class="keyword">null</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//第三步，依次调用对应的构造函数。</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">NioEventLoopGroup</span><span class="params">(<span class="keyword">int</span> nThreads, Executor executor)</span> </span>&#123;</span><br><span class="line">    <span class="comment">//这里又多了一个参数provider，它的作用就是创建每个NioEventLoop对应的selector,对于这个，后面我们会讲到，不需要着急。</span></span><br><span class="line">    <span class="keyword">this</span>(nThreads, executor, SelectorProvider.provider());</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//第四步，依次调用对应的构造函数。</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">NioEventLoopGroup</span><span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function">    <span class="keyword">int</span> nThreads, Executor executor, <span class="keyword">final</span> SelectorProvider selectorProvider)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">this</span>(nThreads, executor, selectorProvider, DefaultSelectStrategyFactory.INSTANCE);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//第五步，依次调用对应的构造函数。</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">NioEventLoopGroup</span><span class="params">(<span class="keyword">int</span> nThreads, Executor executor, <span class="keyword">final</span> SelectorProvider selectorProvider,</span></span></span><br><span class="line"><span class="params"><span class="function">                         <span class="keyword">final</span> SelectStrategyFactory selectStrategyFactory)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">super</span>(nThreads, executor, selectorProvider, selectStrategyFactory, RejectedExecutionHandlers.reject());</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//MultithreadEventLoopGroup.java</span></span><br><span class="line"><span class="comment">//第六步，依次调用对应的构造函数。</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="title">MultithreadEventLoopGroup</span><span class="params">(<span class="keyword">int</span> nThreads, Executor executor, Object... args)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">super</span>(nThreads == <span class="number">0</span> ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//MultithreadEventExecutorGroup.java</span></span><br><span class="line"><span class="comment">//第七步，依次调用对应的构造函数。</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="title">MultithreadEventExecutorGroup</span><span class="params">(<span class="keyword">int</span> nThreads, Executor executor, Object... args)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">this</span>(nThreads, executor, DefaultEventExecutorChooserFactory.INSTANCE, args);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//第八步，依次调用对应的构造函数。最终来到了我们重要的地方。</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="title">MultithreadEventExecutorGroup</span><span class="params">(<span class="keyword">int</span> nThreads, Executor executor,</span></span></span><br><span class="line"><span class="params"><span class="function">                                        EventExecutorChooserFactory chooserFactory, Object... args)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (nThreads &lt;= <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException(String.format(<span class="string">&quot;nThreads: %d (expected: &gt; 0)&quot;</span>, nThreads));</span><br><span class="line">    &#125;</span><br><span class="line">	</span><br><span class="line">    <span class="comment">//这里就是第一步，创建一个线程创建器。作用在上面的注释已经解释过了，是用于创建NioEventLoop底层的线程。</span></span><br><span class="line">    <span class="keyword">if</span> (executor == <span class="keyword">null</span>) &#123;</span><br><span class="line">        <span class="comment">//newDefaultThreadFactory()就是去创建线程。</span></span><br><span class="line">        executor = <span class="keyword">new</span> ThreadPerTaskExecutor(newDefaultThreadFactory());</span><br><span class="line">    &#125;</span><br><span class="line">	</span><br><span class="line">    <span class="comment">//创建对用线程数量的EventExecutor数组</span></span><br><span class="line">    children = <span class="keyword">new</span> EventExecutor[nThreads];</span><br><span class="line"></span><br><span class="line">    <span class="comment">//通过一个for循环，去创建一个相同数量的NioEventLoop</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; nThreads; i ++) &#123;</span><br><span class="line">        <span class="keyword">boolean</span> success = <span class="keyword">false</span>;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">//我们来看看这个newChild做了什么事情</span></span><br><span class="line">            children[i] = newChild做了什么事情(executor, args);</span><br><span class="line">            success = <span class="keyword">true</span>;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            <span class="comment">// <span class="doctag">TODO:</span> Think about if this is a good exception type</span></span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> IllegalStateException(<span class="string">&quot;failed to create a child event loop&quot;</span>, e);</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            <span class="comment">//这里当创建失败的时候，去关闭。</span></span><br><span class="line">            <span class="keyword">if</span> (!success) &#123;</span><br><span class="line">                <span class="keyword">for</span> (<span class="keyword">int</span> j = <span class="number">0</span>; j &lt; i; j ++) &#123;</span><br><span class="line">                    children[j].shutdownGracefully();</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                <span class="keyword">for</span> (<span class="keyword">int</span> j = <span class="number">0</span>; j &lt; i; j ++) &#123;</span><br><span class="line">                    EventExecutor e = children[j];</span><br><span class="line">                    <span class="keyword">try</span> &#123;</span><br><span class="line">                        <span class="keyword">while</span> (!e.isTerminated()) &#123;</span><br><span class="line">                            e.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125; <span class="keyword">catch</span> (InterruptedException interrupted) &#123;</span><br><span class="line">                        <span class="comment">// Let the caller handle the interruption.</span></span><br><span class="line">                        Thread.currentThread().interrupt();</span><br><span class="line">                        <span class="keyword">break</span>;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">	<span class="comment">//第三步，通过这个工厂类创建一个线程选择器。</span></span><br><span class="line">    chooser = chooserFactory.newChooser(children);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">final</span> FutureListener&lt;Object&gt; terminationListener = <span class="keyword">new</span> FutureListener&lt;Object&gt;() &#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">operationComplete</span><span class="params">(Future&lt;Object&gt; future)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">            <span class="keyword">if</span> (terminatedChildren.incrementAndGet() == children.length) &#123;</span><br><span class="line">                terminationFuture.setSuccess(<span class="keyword">null</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (EventExecutor e: children) &#123;</span><br><span class="line">        e.terminationFuture().addListener(terminationListener);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    Set&lt;EventExecutor&gt; childrenSet = <span class="keyword">new</span> LinkedHashSet&lt;EventExecutor&gt;(children.length);</span><br><span class="line">    Collections.addAll(childrenSet, children);</span><br><span class="line">    readonlyChildren = Collections.unmodifiableSet(childrenSet);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>上面源代码中的注释，我们知道上述这个三个步骤是在源码中的哪个地方，那么我们现在来深入分析这个三个步骤吧。</p>
<h3 id="Executor"><a href="#Executor" class="headerlink" title="Executor"></a>Executor</h3><p>我们通过上述代码知道，<code>executor</code>开始的时候是null，通过<code>executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());</code>进行赋值。首先我们看看这个<code>newDefaultThreadFactory();</code>做了什么事情，代码如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//MultithreadEventExecutorGroup.java</span></span><br><span class="line"><span class="comment">//第一步，创建了一个默认的线程创建工厂类。</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> ThreadFactory <span class="title">newDefaultThreadFactory</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> DefaultThreadFactory(getClass());</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//DefaultThreadFactory.java</span></span><br><span class="line"><span class="comment">//第二步，进入对应的构造函数。</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">DefaultThreadFactory</span><span class="params">(Class&lt;?&gt; poolType)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">this</span>(poolType, <span class="keyword">false</span>, Thread.NORM_PRIORITY);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//第三步，进入对应的构造函数。</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">DefaultThreadFactory</span><span class="params">(Class&lt;?&gt; poolType, <span class="keyword">boolean</span> daemon, <span class="keyword">int</span> priority)</span> </span>&#123;</span><br><span class="line">    <span class="comment">//这里的toPoolName方法就是将class的全类名通过StringUtil.simpleClassName(poolType);进行简化，去除了前面的包名称。</span></span><br><span class="line">    <span class="comment">//如果转化之后成className长度为0，那么就返回字符串&quot;unknown&quot;。</span></span><br><span class="line">    <span class="comment">//如果转化之后成className长度为1，那么就把lassName转成小写，然后返回。</span></span><br><span class="line">    <span class="comment">//否则，如果className第一个字母是大写，并且第二个字母是小写时。那么久将第一个字母转化成小写然后返回，如果不是，那么就直接返回className。</span></span><br><span class="line">    <span class="keyword">this</span>(toPoolName(poolType), daemon, priority);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">DefaultThreadFactory</span><span class="params">(String poolName, <span class="keyword">boolean</span> daemon, <span class="keyword">int</span> priority, ThreadGroup threadGroup)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (poolName == <span class="keyword">null</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> NullPointerException(<span class="string">&quot;poolName&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (priority &lt; Thread.MIN_PRIORITY || priority &gt; Thread.MAX_PRIORITY) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException(</span><br><span class="line">            <span class="string">&quot;priority: &quot;</span> + priority + <span class="string">&quot; (expected: Thread.MIN_PRIORITY &lt;= priority &lt;= Thread.MAX_PRIORITY)&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">	<span class="comment">//这里就是保存线程名称的前缀。也就是 nioEventLoopGroup-id-</span></span><br><span class="line">    <span class="comment">//注意，有的文章和视频把nioEventLoopGroup说成了nioEventLoop。这是错的，因为整体都是从 new NioEventLoopGroup();开始的，所以getCalss()</span></span><br><span class="line">    <span class="comment">//自然而然就是NioEventLoopGroup 而不是NioEventLoop，这里注意一下就行，我把debug模式下的内容截图下来了，有图有真相~</span></span><br><span class="line">    prefix = poolName + <span class="string">&#x27;-&#x27;</span> + poolId.incrementAndGet() + <span class="string">&#x27;-&#x27;</span>;</span><br><span class="line">    <span class="keyword">this</span>.daemon = daemon;</span><br><span class="line">    <span class="keyword">this</span>.priority = priority;</span><br><span class="line">    <span class="keyword">this</span>.threadGroup = threadGroup;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>下面这张图就是为了说明这个poolName是nioEventLoopGroup 而并非是其他文章和视频所说的nioEventLoop。其实真正阅读和调试过源码的人不会出现这种低级错误，所以，懂得都懂~</p>
<p><img src="https://gitee.com/zeroable/ima/raw/master/img/image-20211028204054653.png" alt="image-20211028204054653"></p>
<p>上面我们分析过了创建线程的DefaultThreadFactory的构造函数了，那么我们接下来看看这个<code>ThreadPerTaskExecutor</code>做了什么事情吧：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//ThreadPerTaskExecutor.java</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">ThreadPerTaskExecutor</span><span class="params">(ThreadFactory threadFactory)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (threadFactory == <span class="keyword">null</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> NullPointerException(<span class="string">&quot;threadFactory&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//就是把创建的线程创建工厂类保存起来。</span></span><br><span class="line">    <span class="keyword">this</span>.threadFactory = threadFactory;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">execute</span><span class="params">(Runnable command)</span> </span>&#123;</span><br><span class="line">    <span class="comment">//每次执行task任务时，都会创建一个新的线程，去执行task。</span></span><br><span class="line">    threadFactory.newThread(command).start();</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//DefaultThreadFactory.java</span></span><br><span class="line"><span class="comment">//通过这里的newThread方法去创建一个新的线程。</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> Thread <span class="title">newThread</span><span class="params">(Runnable r)</span> </span>&#123;</span><br><span class="line">    <span class="comment">//这里netty使用的自己的一个DefaultRunnableDecorator,它implements了Runnable类。 </span></span><br><span class="line">    Thread t = newThread(<span class="keyword">new</span> DefaultRunnableDecorator(r), prefix + nextId.incrementAndGet());</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (t.isDaemon()) &#123;</span><br><span class="line">            <span class="keyword">if</span> (!daemon) &#123;</span><br><span class="line">                t.setDaemon(<span class="keyword">false</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">if</span> (daemon) &#123;</span><br><span class="line">                t.setDaemon(<span class="keyword">true</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (t.getPriority() != priority) &#123;</span><br><span class="line">            t.setPriority(priority);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (Exception ignored) &#123;</span><br><span class="line">        <span class="comment">// Doesn&#x27;t matter even if failed to set.</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> t;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//这里它自己创建了一个FastThreadLocalThread，它继承自Thread，netty这么做的目的，就是对Thread中的ThreadLocal做进一步的优化。</span></span><br><span class="line"><span class="comment">//后续会讲到这个，感兴趣的可以自己先去研究研究。</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> Thread <span class="title">newThread</span><span class="params">(Runnable r, String name)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> FastThreadLocalThread(threadGroup, r, name);</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p> 到这里，我们知道了这个ThreadPerTaskExecutor做了什么事情。他主要是在每次执行任务的时候，都会创建一个线程实例。并且线程名称的规则就是nioEventLoopGroup-poolId-threadId 这样的格式。</p>
<h3 id="newChild"><a href="#newChild" class="headerlink" title="newChild()"></a>newChild()</h3><p>上面讲到<code>newChild()</code> 是去创建NioEventLoop，其中NioEventLoop构造函数做了以下几件事情：</p>
<ol>
<li>保存上面创建的线程执行器ThreadPerTaskExecutor。</li>
<li>创建一个队列MpscQueue。</li>
<li>创建一个selector。</li>
</ol>
<p>我们来看看具体实现：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//NioEventLoopGroup.java</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> EventLoop <span class="title">newChild</span><span class="params">(Executor executor, Object... args)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> NioEventLoop(<span class="keyword">this</span>, executor, (SelectorProvider) args[<span class="number">0</span>],</span><br><span class="line">                            ((SelectStrategyFactory) args[<span class="number">1</span>]).newSelectStrategy(), (RejectedExecutionHandler) args[<span class="number">2</span>]);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//NioEventLoop.java</span></span><br><span class="line">NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider,</span><br><span class="line">             SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler) &#123;</span><br><span class="line">    <span class="keyword">super</span>(parent, executor, <span class="keyword">false</span>, DEFAULT_MAX_PENDING_TASKS, rejectedExecutionHandler);</span><br><span class="line">    <span class="keyword">if</span> (selectorProvider == <span class="keyword">null</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> NullPointerException(<span class="string">&quot;selectorProvider&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (strategy == <span class="keyword">null</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> NullPointerException(<span class="string">&quot;selectStrategy&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//这里就进行一些保存变量操作。</span></span><br><span class="line">    provider = selectorProvider;</span><br><span class="line">    <span class="comment">//第三步，通过openSelector();创建一个selector。那么也就是一个NioEventLoop对应一个selector。</span></span><br><span class="line">    selector = openSelector();</span><br><span class="line">    selectStrategy = strategy;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//SingleThreadEventLoop.java</span></span><br><span class="line"><span class="comment">//这里就是第二步</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="title">SingleThreadEventLoop</span><span class="params">(EventLoopGroup parent, Executor executor,</span></span></span><br><span class="line"><span class="params"><span class="function">                                <span class="keyword">boolean</span> addTaskWakesUp, <span class="keyword">int</span> maxPendingTasks,</span></span></span><br><span class="line"><span class="params"><span class="function">                                RejectedExecutionHandler rejectedExecutionHandler)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">super</span>(parent, executor, addTaskWakesUp, maxPendingTasks, rejectedExecutionHandler);</span><br><span class="line">    tailTasks = newTaskQueue(maxPendingTasks);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//SingleThreadEventExecutor.java</span></span><br><span class="line"><span class="comment">//这里就是第一步，，创建了taskQueue。</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="title">SingleThreadEventExecutor</span><span class="params">(EventExecutorGroup parent, Executor executor,</span></span></span><br><span class="line"><span class="params"><span class="function">                                    <span class="keyword">boolean</span> addTaskWakesUp, <span class="keyword">int</span> maxPendingTasks,</span></span></span><br><span class="line"><span class="params"><span class="function">                                    RejectedExecutionHandler rejectedHandler)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">super</span>(parent);</span><br><span class="line">    <span class="keyword">this</span>.addTaskWakesUp = addTaskWakesUp;</span><br><span class="line">    <span class="keyword">this</span>.maxPendingTasks = Math.max(<span class="number">16</span>, maxPendingTasks);</span><br><span class="line">    <span class="keyword">this</span>.executor = ObjectUtil.checkNotNull(executor, <span class="string">&quot;executor&quot;</span>);</span><br><span class="line">    taskQueue = newTaskQueue(<span class="keyword">this</span>.maxPendingTasks);</span><br><span class="line">    rejectedExecutionHandler = ObjectUtil.checkNotNull(rejectedHandler, <span class="string">&quot;rejectedHandler&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> Queue&lt;Runnable&gt; <span class="title">newTaskQueue</span><span class="params">(<span class="keyword">int</span> maxPendingTasks)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// This event loop never calls takeTask()</span></span><br><span class="line">    <span class="comment">//multiple producers and a single consumer，这里multiple producers 对应的就是外部线程。consumer就是对应NioEventLoop.</span></span><br><span class="line">    <span class="comment">//也就是说外部线程(非NioEventLoop线程执行)可以把任务交给NioEventLoop去消费。</span></span><br><span class="line">    <span class="keyword">return</span> PlatformDependent.newMpscQueue(maxPendingTasks);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>那么我们NioEventLoop的创建过程就搞明白了，但是创建了这么多个NioEventLoop，那么我们该怎么去选择哪一个NioEventLoop去执行任务呢？这时候线程选择器就至关重要了，netty就是通过<code>chooser = chooserFactory.newChooser(children);</code>来创建一个线程选择器。</p>
<h3 id="newChooser"><a href="#newChooser" class="headerlink" title="newChooser()"></a>newChooser()</h3><p>而这个chooser就是为了给每一个新连接绑定对应的NioEventLoop，那么对应的方法就在NioEventLoopGroup中的<code>next();</code>方法中，我们一起来看下吧~</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//MultithreadEventExecutorGroup.java  NioEventLoopGroup的父类。</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> EventExecutor <span class="title">next</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="comment">//其实原理非常的简单。就是依次选择，第一个连接使用第一个NioEventLoop。。。第N个使用第N个NioEventLoop，第N+1个连接就从头开始，使用第一个连接。</span></span><br><span class="line">    <span class="comment">//N为当初创建线程数量。默认为CPU核数的俩倍。</span></span><br><span class="line">    <span class="comment">//这里netty也做了一些优化，就是判断这个N是否为2的n次幂。如果是2的n次幂，那么就直接使用位操作进行选择，否则使用我们的取余操作。</span></span><br><span class="line">    <span class="comment">//这里其实chooserFactory.newChooser(children);会判断，然后创建俩个不同的实体。具体实现方法如下：</span></span><br><span class="line">    <span class="keyword">return</span> chooser.next();</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//DefaultEventExecutorChooserFactory.java</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> EventExecutorChooser <span class="title">newChooser</span><span class="params">(EventExecutor[] executors)</span> </span>&#123;</span><br><span class="line">    <span class="comment">//判断executors.length是否为2的n次方</span></span><br><span class="line">    <span class="keyword">if</span> (isPowerOfTwo(executors.length)) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> PowerOfTowEventExecutorChooser(executors);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> GenericEventExecutorChooser(executors);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">PowerOfTowEventExecutorChooser</span> <span class="keyword">implements</span> <span class="title">EventExecutorChooser</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> AtomicInteger idx = <span class="keyword">new</span> AtomicInteger();</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> EventExecutor[] executors;</span><br><span class="line"></span><br><span class="line">    PowerOfTowEventExecutorChooser(EventExecutor[] executors) &#123;</span><br><span class="line">        <span class="keyword">this</span>.executors = executors;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> EventExecutor <span class="title">next</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="comment">//这里直接就进行位操作来选择，速度会比取余快上很多，但前提是executors.length必须是2的n次方</span></span><br><span class="line">        <span class="comment">//这里不明白的可以去多试几次。就明白了。</span></span><br><span class="line">        <span class="keyword">return</span> executors[idx.getAndIncrement() &amp; executors.length - <span class="number">1</span>];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">GenericEventExecutorChooser</span> <span class="keyword">implements</span> <span class="title">EventExecutorChooser</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> AtomicInteger idx = <span class="keyword">new</span> AtomicInteger();</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> EventExecutor[] executors;</span><br><span class="line"></span><br><span class="line">    GenericEventExecutorChooser(EventExecutor[] executors) &#123;</span><br><span class="line">        <span class="keyword">this</span>.executors = executors;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> EventExecutor <span class="title">next</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="comment">//这里直接就只能进行取余操作了，因为executors.length不是2的n次方</span></span><br><span class="line">        <span class="keyword">return</span> executors[Math.abs(idx.getAndIncrement() % executors.length)];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>到这里，我们就弄懂了NioEventLoop的创建，以及如果去选择哪一个NioEventLoop去实行任务。那么我们接下来去了解NioEventLoop是怎么被启动起来的。</p>
<h2 id="NioEventLoop-启动"><a href="#NioEventLoop-启动" class="headerlink" title="NioEventLoop 启动"></a>NioEventLoop 启动</h2><p>这里有俩个地方启动了，第一个就是我们之前说的绑定端口的时候，其实就是在NioEventLoop中的线程进行创建的。第二个就是新连接接入时，通过chooser来绑定NioEventLoop。这个后续会有文章详细讲解新连接接入流程。这里就以第一个为例，服务端绑定端口的时候，就是在<code>doBind0();</code>方法中调用了<code>executor();</code> 我们一起来回忆一吧：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//AbstractBootstrap.java</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">doBind0</span><span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function">    <span class="keyword">final</span> ChannelFuture regFuture, <span class="keyword">final</span> Channel channel,</span></span></span><br><span class="line"><span class="params"><span class="function">    <span class="keyword">final</span> SocketAddress localAddress, <span class="keyword">final</span> ChannelPromise promise)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// This method is invoked before channelRegistered() is triggered.  Give user handlers a chance to set up</span></span><br><span class="line">    <span class="comment">// the pipeline in its channelRegistered() implementation.</span></span><br><span class="line">    <span class="comment">//这里就是使用了NioEventLoop的线程去执行绑定操作。</span></span><br><span class="line">    channel.eventLoop().execute(<span class="keyword">new</span> Runnable() &#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">            <span class="keyword">if</span> (regFuture.isSuccess()) &#123;</span><br><span class="line">                channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                promise.setFailure(regFuture.cause());</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//SingleThreadEventExecutor.java</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">execute</span><span class="params">(Runnable task)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (task == <span class="keyword">null</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> NullPointerException(<span class="string">&quot;task&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">	<span class="comment">//判断当前执行的线程是否是NioEventLoop线程中，其实服务端启动的过程中，是通过主线程去启动的，也就是main线程。所以inEventLoop为false。</span></span><br><span class="line">    <span class="keyword">boolean</span> inEventLoop = inEventLoop();</span><br><span class="line">    <span class="keyword">if</span> (inEventLoop) &#123;</span><br><span class="line">        addTask(task);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        startThread();</span><br><span class="line">        addTask(task);</span><br><span class="line">        <span class="keyword">if</span> (isShutdown() &amp;&amp; removeTask(task)) &#123;</span><br><span class="line">            reject();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (!addTaskWakesUp &amp;&amp; wakesUpForTask(task)) &#123;</span><br><span class="line">        wakeup(inEventLoop);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//AbstractEventExecutor.java</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">inEventLoop</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="comment">//将当前线程传入，这里当前线程为main。</span></span><br><span class="line">    <span class="keyword">return</span> inEventLoop(Thread.currentThread());</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//SingleThreadEventExecutor.java</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">inEventLoop</span><span class="params">(Thread thread)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> thread == <span class="keyword">this</span>.thread;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">startThread</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="comment">//判断当前线程是否是未启动。</span></span><br><span class="line">    <span class="keyword">if</span> (STATE_UPDATER.get(<span class="keyword">this</span>) == ST_NOT_STARTED) &#123;</span><br><span class="line">        <span class="comment">//通过一个cas操作，来进行开启线程，这里就是防止多个线程同时调用同一个NioEventLoop实例执行这段代码，造成线程不安全。</span></span><br><span class="line">        <span class="keyword">if</span> (STATE_UPDATER.compareAndSet(<span class="keyword">this</span>, ST_NOT_STARTED, ST_STARTED)) &#123;</span><br><span class="line">            <span class="comment">//然后我们来看看这个方法</span></span><br><span class="line">            doStartThread();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">doStartThread</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">assert</span> thread == <span class="keyword">null</span>;</span><br><span class="line">    <span class="comment">//这里其实就是通过executor的execute方法。我们前面分析过了，它其实就是通过那个ThreadFactory进行创建一个新的线程去执行这个任务。</span></span><br><span class="line">    executor.execute(<span class="keyword">new</span> Runnable() &#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">            <span class="comment">//拿到当前线程，并保存。这里就是进行NioEventLoop 和线程进行唯一的绑定。</span></span><br><span class="line">            thread = Thread.currentThread();</span><br><span class="line">            <span class="keyword">if</span> (interrupted) &#123;</span><br><span class="line">                thread.interrupt();</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">boolean</span> success = <span class="keyword">false</span>;</span><br><span class="line">            updateLastExecutionTime();</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="comment">//然后去调用NioEventLoop的run方法。这里就是实际的调用。</span></span><br><span class="line">                SingleThreadEventExecutor.<span class="keyword">this</span>.run();</span><br><span class="line">                success = <span class="keyword">true</span>;</span><br><span class="line">            &#125; <span class="keyword">catch</span> (Throwable t) &#123;</span><br><span class="line">                logger.warn(<span class="string">&quot;Unexpected exception from an event executor: &quot;</span>, t);</span><br><span class="line">            &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">                    <span class="keyword">int</span> oldState = STATE_UPDATER.get(SingleThreadEventExecutor.<span class="keyword">this</span>);</span><br><span class="line">                    <span class="keyword">if</span> (oldState &gt;= ST_SHUTTING_DOWN || STATE_UPDATER.compareAndSet(</span><br><span class="line">                        SingleThreadEventExecutor.<span class="keyword">this</span>, oldState, ST_SHUTTING_DOWN)) &#123;</span><br><span class="line">                        <span class="keyword">break</span>;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                <span class="comment">// Check if confirmShutdown() was called at the end of the loop.</span></span><br><span class="line">                <span class="keyword">if</span> (success &amp;&amp; gracefulShutdownStartTime == <span class="number">0</span>) &#123;</span><br><span class="line">                    logger.error(<span class="string">&quot;Buggy &quot;</span> + EventExecutor.class.getSimpleName() + <span class="string">&quot; implementation; &quot;</span> +</span><br><span class="line">                                 SingleThreadEventExecutor.class.getSimpleName() + <span class="string">&quot;.confirmShutdown() must be called &quot;</span> +</span><br><span class="line">                                 <span class="string">&quot;before run() implementation terminates.&quot;</span>);</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    <span class="comment">// Run all remaining tasks and shutdown hooks.</span></span><br><span class="line">                    <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">                        <span class="keyword">if</span> (confirmShutdown()) &#123;</span><br><span class="line">                            <span class="keyword">break</span>;</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                    <span class="keyword">try</span> &#123;</span><br><span class="line">                        cleanup();</span><br><span class="line">                    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                        STATE_UPDATER.set(SingleThreadEventExecutor.<span class="keyword">this</span>, ST_TERMINATED);</span><br><span class="line">                        threadLock.release();</span><br><span class="line">                        <span class="keyword">if</span> (!taskQueue.isEmpty()) &#123;</span><br><span class="line">                            logger.warn(</span><br><span class="line">                                <span class="string">&quot;An event executor terminated with &quot;</span> +</span><br><span class="line">                                <span class="string">&quot;non-empty task queue (&quot;</span> + taskQueue.size() + <span class="string">&#x27;)&#x27;</span>);</span><br><span class="line">                        &#125;</span><br><span class="line"></span><br><span class="line">                        terminationFuture.setSuccess(<span class="keyword">null</span>);</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>到这里，我们知道了NioEventLoop的启动过程，我们以服务端启动时绑定端口进行举例，通过<code>eventLoop.execute</code>进行启动。最后的执行就是在NioEventLoop中的run();方法。</p>
<h2 id="NioEventLoop-IO-事件的检测和处理"><a href="#NioEventLoop-IO-事件的检测和处理" class="headerlink" title="NioEventLoop IO 事件的检测和处理"></a>NioEventLoop IO 事件的检测和处理</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">//这里进行轮询IO事件。</span></span><br><span class="line">            <span class="keyword">switch</span> (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) &#123;</span><br><span class="line">                <span class="keyword">case</span> SelectStrategy.CONTINUE:</span><br><span class="line">                    <span class="keyword">continue</span>;</span><br><span class="line">                <span class="keyword">case</span> SelectStrategy.SELECT:</span><br><span class="line">                    <span class="comment">// 重点在这里。这里有一个wakenUp标识，标识当前select操作是否被唤醒。每次进行select操作时</span></span><br><span class="line">                    <span class="comment">// 将标识置为false。表示需要进行select操作，并且表示是未唤醒状态。</span></span><br><span class="line">                    <span class="comment">// 这里wakeUp是可以被外部线程唤醒的。 </span></span><br><span class="line">                    select(wakenUp.getAndSet(<span class="keyword">false</span>));</span><br><span class="line">                    <span class="keyword">if</span> (wakenUp.get()) &#123;</span><br><span class="line">                        selector.wakeup();</span><br><span class="line">                    &#125;</span><br><span class="line">                <span class="keyword">default</span>:</span><br><span class="line">                    <span class="comment">// fallthrough</span></span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">//暂时不重要的代码进行省略</span></span><br><span class="line">            processSelectedKeys();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>从上面代码中，我们知道，select方法是最重要的，所以我们来看看select方法到底做了什么吧。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">select</span><span class="params">(<span class="keyword">boolean</span> oldWakenUp)</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">    Selector selector = <span class="keyword">this</span>.selector;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">int</span> selectCnt = <span class="number">0</span>;</span><br><span class="line">        <span class="comment">// 首先，获取当前时间。</span></span><br><span class="line">        <span class="keyword">long</span> currentTimeNanos = System.nanoTime();</span><br><span class="line">        <span class="comment">// 然后当前时间加上截止时间。这里我们需要知道，NioEventLoop底层有一个定时任务队列，如果感兴趣的可以先去了解一下，</span></span><br><span class="line">        <span class="comment">// 后面会说到，这个定时任务队列就是按照任务的截止时间排序的一个具有优先级别的队列。这里的delayNanos方法就是用来计算这个定时任务</span></span><br><span class="line">        <span class="comment">// 队列第一个任务的截止时间，这里后面会说到，感兴趣的自己可以先去了解一下。所以这个selectDeadLineNanos就是当前执行select操作最长不能超过selectDeadLineNanos时间。</span></span><br><span class="line">        <span class="keyword">long</span> selectDeadLineNanos = currentTimeNanos + delayNanos(currentTimeNanos);</span><br><span class="line">        <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">            <span class="comment">// 先获取超时时间，如果操作超时了，并且一次也没有进行select,那么就进行一个非阻塞的selectNow。并且将selectCnt设置为1。</span></span><br><span class="line">            </span><br><span class="line">            <span class="keyword">long</span> timeoutMillis = (selectDeadLineNanos - currentTimeNanos + <span class="number">500000L</span>) / <span class="number">1000000L</span>;</span><br><span class="line">            <span class="keyword">if</span> (timeoutMillis &lt;= <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="keyword">if</span> (selectCnt == <span class="number">0</span>) &#123;</span><br><span class="line">                    selector.selectNow();</span><br><span class="line">                    selectCnt = <span class="number">1</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="comment">// If a task was submitted when wakenUp value was true, the task didn&#x27;t get a chance to call</span></span><br><span class="line">            <span class="comment">// Selector#wakeup. So we need to check task queue again before executing select operation.</span></span><br><span class="line">            <span class="comment">// If we don&#x27;t, the task might be pended until select operation was timed out.</span></span><br><span class="line">            <span class="comment">// It might be pended until idle timeout if IdleStateHandler existed in pipeline.</span></span><br><span class="line">            <span class="comment">//如果没有到截止时间，如果当前有任务，也就是taskQueue不为空，并且通过cas操作将wakeUp在select执行前改为false改为true,注意，这个wakeUp是可以被外部线程修改的，也就是select操作是可以被外部线程唤醒的，所以这里到这里前没有被外部线程唤醒过。</span></span><br><span class="line">            <span class="comment">// 如果俩个条件都满足要求，那么就执行一个非阻塞的select操作，并将selectCnt设置为1，也就是select次数。然后跳出循环，结束此次select操作。</span></span><br><span class="line">            <span class="keyword">if</span> (hasTasks() &amp;&amp; wakenUp.compareAndSet(<span class="keyword">false</span>, <span class="keyword">true</span>)) &#123;</span><br><span class="line">                selector.selectNow();</span><br><span class="line">                selectCnt = <span class="number">1</span>;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">			<span class="comment">// 如果当前任务队列为空，并且阻塞时间没到，那么就进行一个阻塞式的select。</span></span><br><span class="line">            <span class="comment">// 这个selectedKeys表示轮询到的事件，如果等于0，表示当前没有事件。</span></span><br><span class="line">            <span class="keyword">int</span> selectedKeys = selector.select(timeoutMillis);</span><br><span class="line">            <span class="comment">// 轮询次数加一。</span></span><br><span class="line">            selectCnt ++;</span><br><span class="line">			<span class="comment">// 也就是当满足如下任意一个条件时，就会跳出循环，结束select操作：</span></span><br><span class="line">            <span class="comment">// 1. 当前轮询到了事件，也就是selectedKeys不等于0.</span></span><br><span class="line">            <span class="comment">// 2. 这个select操作需要被唤醒。</span></span><br><span class="line">            <span class="comment">// 3. 当前wakeUp被外部线程修改，表示这个select操作需要被唤醒</span></span><br><span class="line">            <span class="comment">// 4. 任务队列中有任务。</span></span><br><span class="line">            <span class="comment">// 5. 定时任务队列中存在任务。</span></span><br><span class="line">            </span><br><span class="line">            <span class="keyword">if</span> (selectedKeys != <span class="number">0</span> || oldWakenUp || wakenUp.get() || hasTasks() || hasScheduledTasks()) &#123;</span><br><span class="line">                <span class="comment">// - Selected something,</span></span><br><span class="line">                <span class="comment">// - waken up by user, or</span></span><br><span class="line">                <span class="comment">// - the task queue has a pending task.</span></span><br><span class="line">                <span class="comment">// - a scheduled task is ready for processing</span></span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// 如果当前线程被中断。</span></span><br><span class="line">            <span class="keyword">if</span> (Thread.interrupted()) &#123;</span><br><span class="line">                <span class="comment">// Thread was interrupted so reset selected keys and break so we not run into a busy loop.</span></span><br><span class="line">                <span class="comment">// As this is most likely a bug in the handler of the user or it&#x27;s client library we will</span></span><br><span class="line">                <span class="comment">// also log it.</span></span><br><span class="line">                <span class="comment">//</span></span><br><span class="line">                <span class="comment">// See https://github.com/netty/netty/issues/2426</span></span><br><span class="line">                <span class="keyword">if</span> (logger.isDebugEnabled()) &#123;</span><br><span class="line">                    logger.debug(<span class="string">&quot;Selector.select() returned prematurely because &quot;</span> +</span><br><span class="line">                                 <span class="string">&quot;Thread.currentThread().interrupt() was called. Use &quot;</span> +</span><br><span class="line">                                 <span class="string">&quot;NioEventLoop.shutdownGracefully() to shutdown the NioEventLoop.&quot;</span>);</span><br><span class="line">                &#125;</span><br><span class="line">                selectCnt = <span class="number">1</span>;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">			<span class="comment">//这里就对jdk空轮询的bug用一种巧妙的方式给解决了。敲黑板，这里面试经常问到。</span></span><br><span class="line">            <span class="keyword">long</span> time = System.nanoTime();</span><br><span class="line">            <span class="comment">// 这里就是表示如果阻塞完后的当前之间与开始时间之间的时间间隔超过了超时时间，那么此次select结束。</span></span><br><span class="line">            <span class="keyword">if</span> (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) &gt;= currentTimeNanos) &#123;</span><br><span class="line">                <span class="comment">// timeoutMillis elapsed without anything selected.</span></span><br><span class="line">                <span class="comment">//到这里，那么就意味着已经执行过一次阻塞式的select操作了。</span></span><br><span class="line">                selectCnt = <span class="number">1</span>;</span><br><span class="line">                <span class="comment">// 那么反过来说明了本来需要阻塞timeoutMillis那么久，但是实际上并没有阻塞这么久，select方法就被唤醒了。</span></span><br><span class="line">                <span class="comment">// 这里极限情况下就造成了空轮询，因为在一些极端情况下，可能select并没有进行阻塞，立马返回了，这就导致了一直在死循环</span></span><br><span class="line">                <span class="comment">// 这就导致了空轮询，照成了cpu飙升100%。这种极端情况之一就是连接出现了RST，</span></span><br><span class="line">                <span class="comment">// 因为poll和epoll对于突然中断的连接socket会对返回的eventSet事件集合置为POLLHUP或者POLLERR，</span></span><br><span class="line">                <span class="comment">// eventSet事件集合发生了变化，这就导致Selector会被唤醒，进而导致CPU 100%问题。</span></span><br><span class="line">                <span class="comment">// 根本原因就是JDK没有处理好这种情况，比如SelectionKey中就没定义有异常事件的类型。</span></span><br><span class="line">                <span class="comment">// 上述描述来自于：https://zhuanlan.zhihu.com/p/92133508 可以去自己查看。</span></span><br><span class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (SELECTOR_AUTO_REBUILD_THRESHOLD &gt; <span class="number">0</span> &amp;&amp;</span><br><span class="line">                       selectCnt &gt;= SELECTOR_AUTO_REBUILD_THRESHOLD) &#123;</span><br><span class="line">                <span class="comment">// 那么netty在这里使用了一个非常巧妙的方式，就是这种情况连续空轮询了一定次数后(512次)，说明这个select已经出现了问题，</span></span><br><span class="line">                <span class="comment">// 那么就将selector上原来的事件和属性重新注册到一个新的selector上，从而解决了空轮询的bug。</span></span><br><span class="line">                <span class="comment">// The selector returned prematurely many times in a row.</span></span><br><span class="line">                <span class="comment">// Rebuild the selector to work around the problem.</span></span><br><span class="line">                logger.warn(</span><br><span class="line">                    <span class="string">&quot;Selector.select() returned prematurely &#123;&#125; times in a row; rebuilding Selector &#123;&#125;.&quot;</span>,</span><br><span class="line">                    selectCnt, selector);</span><br><span class="line">				<span class="comment">// 这个方法就不点进去查看了，自己可以去查看一下，原理非常简单，就是重新进行注册到新的selector上，并且重新绑定selector。</span></span><br><span class="line">                rebuildSelector();</span><br><span class="line">                <span class="comment">// 这时候就将局部变量重新赋值。</span></span><br><span class="line">                selector = <span class="keyword">this</span>.selector;</span><br><span class="line">				</span><br><span class="line">                <span class="comment">// Select again to populate selectedKeys.</span></span><br><span class="line">                selector.selectNow();</span><br><span class="line">                selectCnt = <span class="number">1</span>;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            currentTimeNanos = time;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (selectCnt &gt; MIN_PREMATURE_SELECTOR_RETURNS) &#123;</span><br><span class="line">            <span class="keyword">if</span> (logger.isDebugEnabled()) &#123;</span><br><span class="line">                logger.debug(<span class="string">&quot;Selector.select() returned prematurely &#123;&#125; times in a row for Selector &#123;&#125;.&quot;</span>,</span><br><span class="line">                             selectCnt - <span class="number">1</span>, selector);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (CancelledKeyException e) &#123;</span><br><span class="line">        <span class="keyword">if</span> (logger.isDebugEnabled()) &#123;</span><br><span class="line">            logger.debug(CancelledKeyException.class.getSimpleName() + <span class="string">&quot; raised by a Selector &#123;&#125; - JDK bug?&quot;</span>,</span><br><span class="line">                         selector, e);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// Harmless exception - log anyway</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>上面我们重点讲到了select方法，就是当有事件或者有任务或者定时任务时，那么就结束select操作，也就是说，它只是去查找，直到有事件或者任务，才会结束，那么下面我们来说说找到事件之后，怎么去进行处理这些时间，netty就是通过<code>processSelectedKeys();</code>进行处理IO事件的。处理流程大致如下：</p>
<ol>
<li>select keySet 集合的优化，将底层的hashSet通过反射将hashSet替换成数组。那么将select操作的时间复杂度变成了O(1);这个优化放在了NioEventLoop的构造函数中，也就是<code>openSelector();</code>方法。</li>
<li>调用<code>processSelectedKeysOptimized();</code>真正的处理IO事件。</li>
</ol>
<p>我们来看看他的优化过程吧：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> Selector <span class="title">openSelector</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">final</span> Selector selector;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        selector = provider.openSelector();</span><br><span class="line">    &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> ChannelException(<span class="string">&quot;failed to open a new selector&quot;</span>, e);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 这里表示是否需要去进行优化。</span></span><br><span class="line">    <span class="keyword">if</span> (DISABLE_KEYSET_OPTIMIZATION) &#123;</span><br><span class="line">        <span class="keyword">return</span> selector;</span><br><span class="line">    &#125;</span><br><span class="line">	<span class="comment">// 在这里，使用了一个自定义的Set进行保存selectKeys，在4.1.6.Final版本时使用了俩个数组进行交替实现，使用flip进行切换，他只实现了</span></span><br><span class="line">    <span class="keyword">final</span> SelectedSelectionKeySet selectedKeySet = <span class="keyword">new</span> SelectedSelectionKeySet();</span><br><span class="line">	<span class="comment">// 省略暂时不需要的代码，我们直接看看SelectedSelectionKeySet的代码：</span></span><br><span class="line">    <span class="comment">// 他其实还是实现了Set接口，但是其底层实现使用了俩个数组，当前源码版本是4.1.6.Final。</span></span><br><span class="line">    <span class="comment">// 但是在最新的版本中只用到了一个数组，这是因为他想通过俩个数组进行交替实现，但是对于这个SelectedSelectionKeySet只是单线程的处理。</span></span><br><span class="line">    <span class="comment">// 所以有人提了一个issues，作者也认可了，在最新版本修改了，使用了一个数组。</span></span><br><span class="line">    <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">SelectedSelectionKeySet</span> <span class="keyword">extends</span> <span class="title">AbstractSet</span>&lt;<span class="title">SelectionKey</span>&gt; </span>&#123;</span><br><span class="line">        <span class="keyword">private</span> SelectionKey[] keysA;</span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">int</span> keysASize;</span><br><span class="line">        <span class="keyword">private</span> SelectionKey[] keysB;</span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">int</span> keysBSize;</span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">boolean</span> isA = <span class="keyword">true</span>;</span><br><span class="line"></span><br><span class="line">        <span class="comment">//创建了一个大小为1024的数组。</span></span><br><span class="line">        SelectedSelectionKeySet() &#123;</span><br><span class="line">            keysA = <span class="keyword">new</span> SelectionKey[<span class="number">1024</span>];</span><br><span class="line">            keysB = keysA.clone();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 就是在这里进行了一个优化，因为jdk Nio 使用的是一个HashSet，我们知道，HashSet他使用的又是一个Hashmap。</span></span><br><span class="line">        <span class="comment">// HashMap的add方法时间复杂度并不稳定，最坏的可以达到O(n)。所以这里netty作者自己使用数组实现了一个KeySet。</span></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">add</span><span class="params">(SelectionKey o)</span> </span>&#123;</span><br><span class="line">            <span class="keyword">if</span> (o == <span class="keyword">null</span>) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">            &#125;</span><br><span class="line">			<span class="comment">// 这是因为是单线程的，所以可以这样直接操作。</span></span><br><span class="line">            <span class="keyword">if</span> (isA) &#123;</span><br><span class="line">                <span class="keyword">int</span> size = keysASize;</span><br><span class="line">                keysA[size ++] = o;</span><br><span class="line">                keysASize = size;</span><br><span class="line">                <span class="comment">//如果容量不够，就进行扩容。</span></span><br><span class="line">                <span class="keyword">if</span> (size == keysA.length) &#123;</span><br><span class="line">                    doubleCapacityA();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="keyword">int</span> size = keysBSize;</span><br><span class="line">                keysB[size ++] = o;</span><br><span class="line">                keysBSize = size;</span><br><span class="line">                <span class="keyword">if</span> (size == keysB.length) &#123;</span><br><span class="line">                    doubleCapacityB();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">		</span><br><span class="line">        <span class="comment">// 这里没啥好说的，就是对数组进行双倍扩容。</span></span><br><span class="line">        <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">doubleCapacityA</span><span class="params">()</span> </span>&#123;</span><br><span class="line">            SelectionKey[] newKeysA = <span class="keyword">new</span> SelectionKey[keysA.length &lt;&lt; <span class="number">1</span>];</span><br><span class="line">            System.arraycopy(keysA, <span class="number">0</span>, newKeysA, <span class="number">0</span>, keysASize);</span><br><span class="line">            keysA = newKeysA;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">		<span class="comment">// 这里没啥好说的，就是对数组进行双倍扩容。</span></span><br><span class="line">        <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">doubleCapacityB</span><span class="params">()</span> </span>&#123;</span><br><span class="line">            SelectionKey[] newKeysB = <span class="keyword">new</span> SelectionKey[keysB.length &lt;&lt; <span class="number">1</span>];</span><br><span class="line">            System.arraycopy(keysB, <span class="number">0</span>, newKeysB, <span class="number">0</span>, keysBSize);</span><br><span class="line">            keysB = newKeysB;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">//通过这个获取SelectionKey数组，并且获取</span></span><br><span class="line">        SelectionKey[] flip() &#123;</span><br><span class="line">            <span class="keyword">if</span> (isA) &#123;</span><br><span class="line">                isA = <span class="keyword">false</span>;</span><br><span class="line">                keysA[keysASize] = <span class="keyword">null</span>;</span><br><span class="line">                keysBSize = <span class="number">0</span>;</span><br><span class="line">                <span class="keyword">return</span> keysA;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                isA = <span class="keyword">true</span>;</span><br><span class="line">                keysB[keysBSize] = <span class="keyword">null</span>;</span><br><span class="line">                keysASize = <span class="number">0</span>;</span><br><span class="line">                <span class="keyword">return</span> keysB;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">size</span><span class="params">()</span> </span>&#123;</span><br><span class="line">            <span class="keyword">if</span> (isA) &#123;</span><br><span class="line">                <span class="keyword">return</span> keysASize;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="keyword">return</span> keysBSize;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 这三个方法是不支持的，因为使用SelectedSelectionKeySet时，</span></span><br><span class="line">        <span class="comment">// 并不需要使用这三个方法，这也就是为啥能使用数组去实现KeySet。</span></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">remove</span><span class="params">(Object o)</span> </span>&#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">contains</span><span class="params">(Object o)</span> </span>&#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> Iterator&lt;SelectionKey&gt; <span class="title">iterator</span><span class="params">()</span> </span>&#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> UnsupportedOperationException();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>看完<code>SelectedSelectionKeySet</code> 我们继续顺着<code>openSelector();</code>后面的方法查看：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 这里其实就是使用了反射的方式得到SelectorImpl类对象。</span></span><br><span class="line">Object maybeSelectorImplClass = AccessController.doPrivileged(<span class="keyword">new</span> PrivilegedAction&lt;Object&gt;() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> Object <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> Class.forName(</span><br><span class="line">                <span class="string">&quot;sun.nio.ch.SelectorImpl&quot;</span>,</span><br><span class="line">                <span class="keyword">false</span>,</span><br><span class="line">                PlatformDependent.getSystemClassLoader());</span><br><span class="line">        &#125; <span class="keyword">catch</span> (ClassNotFoundException e) &#123;</span><br><span class="line">            <span class="keyword">return</span> e;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (SecurityException e) &#123;</span><br><span class="line">            <span class="keyword">return</span> e;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br><span class="line"><span class="comment">// 这里判断是否是一个Calss对象，并且判断这个selector是否这个maybeSelectorImplClass的一个实现。</span></span><br><span class="line"><span class="keyword">if</span> (!(maybeSelectorImplClass <span class="keyword">instanceof</span> Class) ||</span><br><span class="line">    <span class="comment">// ensure the current selector implementation is what we can instrument.</span></span><br><span class="line">    !((Class&lt;?&gt;) maybeSelectorImplClass).isAssignableFrom(selector.getClass())) &#123;</span><br><span class="line">    <span class="keyword">if</span> (maybeSelectorImplClass <span class="keyword">instanceof</span> Exception) &#123;</span><br><span class="line">        Exception e = (Exception) maybeSelectorImplClass;</span><br><span class="line">        logger.trace(<span class="string">&quot;failed to instrument a special java.util.Set into: &#123;&#125;&quot;</span>, selector, e);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 如果不是他的实现，并且通过反射拿到的selectorImpl类对象并没有报错，那么就直接返回该selector。</span></span><br><span class="line">    <span class="keyword">return</span> selector;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 如果是他的实现。</span></span><br><span class="line"><span class="keyword">final</span> Class&lt;?&gt; selectorImplClass = (Class&lt;?&gt;) maybeSelectorImplClass;</span><br><span class="line"></span><br><span class="line">Object maybeException = AccessController.doPrivileged(<span class="keyword">new</span> PrivilegedAction&lt;Object&gt;() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> Object <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 这里就通过反射的方式，拿到俩个属性，分别是selectedKeys 和 publicSelectedKeys。</span></span><br><span class="line">            Field selectedKeysField = selectorImplClass.getDeclaredField(<span class="string">&quot;selectedKeys&quot;</span>);</span><br><span class="line">            Field publicSelectedKeysField = selectorImplClass.getDeclaredField(<span class="string">&quot;publicSelectedKeys&quot;</span>);</span><br><span class="line">			<span class="comment">// 下面就是标准的通过反射进行赋值。</span></span><br><span class="line">            selectedKeysField.setAccessible(<span class="keyword">true</span>);</span><br><span class="line">            publicSelectedKeysField.setAccessible(<span class="keyword">true</span>);</span><br><span class="line">			<span class="comment">// 将jdk Nio 里面的HashSet替换成netty自己的selectedKeySet。</span></span><br><span class="line">            selectedKeysField.set(selector, selectedKeySet);</span><br><span class="line">            publicSelectedKeysField.set(selector, selectedKeySet);</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (NoSuchFieldException e) &#123;</span><br><span class="line">            <span class="keyword">return</span> e;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IllegalAccessException e) &#123;</span><br><span class="line">            <span class="keyword">return</span> e;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (RuntimeException e) &#123;</span><br><span class="line">            <span class="comment">// JDK 9 can throw an inaccessible object exception here; since Netty compiles</span></span><br><span class="line">            <span class="comment">// against JDK 7 and this exception was only added in JDK 9, we have to weakly</span></span><br><span class="line">            <span class="comment">// check the type</span></span><br><span class="line">            <span class="keyword">if</span> (<span class="string">&quot;java.lang.reflect.InaccessibleObjectException&quot;</span>.equals(e.getClass().getName())) &#123;</span><br><span class="line">                <span class="keyword">return</span> e;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="keyword">throw</span> e;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (maybeException <span class="keyword">instanceof</span> Exception) &#123;</span><br><span class="line">    selectedKeys = <span class="keyword">null</span>;</span><br><span class="line">    Exception e = (Exception) maybeException;</span><br><span class="line">    logger.trace(<span class="string">&quot;failed to instrument a special java.util.Set into: &#123;&#125;&quot;</span>, selector, e);</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="comment">// 这里就将selectedKeySet 保存到NioEventLoop 的成员变量selectedKeys中。</span></span><br><span class="line">    selectedKeys = selectedKeySet;</span><br><span class="line">    logger.trace(<span class="string">&quot;instrumented a special java.util.Set into: &#123;&#125;&quot;</span>, selector);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> selector;</span><br></pre></td></tr></table></figure>

<p>那么到这里，我们就知道了netty对selectedKeys的优化，通过netty自己实现的一个set，将add方法进行优化，使得add方法的时间复杂度降到了<code>O(1)</code>。</p>
<p>然后我们继续往下，最终执行完<code>select();</code>方法后，会执行<code>processSelectedKeys();</code>那么我们查看一下<code>processSelectedKeys();</code>有关的的源码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">processSelectedKeys</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (selectedKeys != <span class="keyword">null</span>) &#123;</span><br><span class="line">        processSelectedKeysOptimized(selectedKeys.flip());</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        processSelectedKeysPlain(selector.selectedKeys());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 最终会调用到这个方法里面。</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">processSelectedKeysOptimized</span><span class="params">(SelectionKey[] selectedKeys)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 遍历selectedKeys</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>;; i ++) &#123;</span><br><span class="line">        <span class="keyword">final</span> SelectionKey k = selectedKeys[i];</span><br><span class="line">        <span class="keyword">if</span> (k == <span class="keyword">null</span>) &#123;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// null out entry in the array to allow to have it GC&#x27;ed once the Channel close</span></span><br><span class="line">        <span class="comment">// See https://github.com/netty/netty/issues/2363</span></span><br><span class="line">        selectedKeys[i] = <span class="keyword">null</span>;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 这里你们是否还记得这个a变量是啥吗？在我们之前注册的时候，将NioServerSocketChannel 当作一个attachment，绑定到了这个Jdk Channel 中，这里可以通过SelectionKey实例拿到。</span></span><br><span class="line">        <span class="keyword">final</span> Object a = k.attachment();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (a <span class="keyword">instanceof</span> AbstractNioChannel) &#123;</span><br><span class="line">            <span class="comment">// 我们看看这个方法的具体实现。</span></span><br><span class="line">            processSelectedKey(k, (AbstractNioChannel) a);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="meta">@SuppressWarnings(&quot;unchecked&quot;)</span></span><br><span class="line">            NioTask&lt;SelectableChannel&gt; task = (NioTask&lt;SelectableChannel&gt;) a;</span><br><span class="line">            processSelectedKey(k, task);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (needsToSelectAgain) &#123;</span><br><span class="line">            <span class="comment">// null out entries in the array to allow to have it GC&#x27;ed once the Channel close</span></span><br><span class="line">            <span class="comment">// See https://github.com/netty/netty/issues/2363</span></span><br><span class="line">            <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">                i++;</span><br><span class="line">                <span class="keyword">if</span> (selectedKeys[i] == <span class="keyword">null</span>) &#123;</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                selectedKeys[i] = <span class="keyword">null</span>;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            selectAgain();</span><br><span class="line">            <span class="comment">// Need to flip the optimized selectedKeys to get the right reference to the array</span></span><br><span class="line">            <span class="comment">// and reset the index to -1 which will then set to 0 on the for loop</span></span><br><span class="line">            <span class="comment">// to start over again.</span></span><br><span class="line">            <span class="comment">//</span></span><br><span class="line">            <span class="comment">// See https://github.com/netty/netty/issues/1523</span></span><br><span class="line">            selectedKeys = <span class="keyword">this</span>.selectedKeys.flip();</span><br><span class="line">            i = -<span class="number">1</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">processSelectedKey</span><span class="params">(SelectionKey k, AbstractNioChannel ch)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 拿到这个channel的unsafe。</span></span><br><span class="line">    <span class="keyword">final</span> AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();</span><br><span class="line">    <span class="comment">// 判断这个SelectionKey是否合法，因为这个连接可能有点问题</span></span><br><span class="line">    <span class="keyword">if</span> (!k.isValid()) &#123;</span><br><span class="line">        <span class="keyword">final</span> EventLoop eventLoop;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            eventLoop = ch.eventLoop();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Throwable ignored) &#123;</span><br><span class="line">            <span class="comment">// If the channel implementation throws an exception because there is no event loop, we ignore this</span></span><br><span class="line">            <span class="comment">// because we are only trying to determine if ch is registered to this event loop and thus has authority</span></span><br><span class="line">            <span class="comment">// to close ch.</span></span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// Only close ch if ch is still registerd to this EventLoop. ch could have deregistered from the event loop</span></span><br><span class="line">        <span class="comment">// and thus the SelectionKey could be cancelled as part of the deregistration process, but the channel is</span></span><br><span class="line">        <span class="comment">// still healthy and should not be closed.</span></span><br><span class="line">        <span class="comment">// See https://github.com/netty/netty/issues/5125</span></span><br><span class="line">        <span class="keyword">if</span> (eventLoop != <span class="keyword">this</span> || eventLoop == <span class="keyword">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// close the channel if the key is not valid anymore</span></span><br><span class="line">        <span class="comment">// 那么就调用unsafe 的close方法。其实就是使用pipeline进行操作。关于pipeline，后面我们会讲到。</span></span><br><span class="line">        unsafe.close(unsafe.voidPromise());</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">// 如果是合法的，就拿到这个SelectionKey上的IO事件。</span></span><br><span class="line">        <span class="keyword">int</span> readyOps = k.readyOps();</span><br><span class="line">        <span class="comment">// We first need to call finishConnect() before try to trigger a read(...) or write(...) as otherwise</span></span><br><span class="line">        <span class="comment">// the NIO JDK channel implementation may throw a NotYetConnectedException.</span></span><br><span class="line">        <span class="comment">//判断这个事件的具体是哪个。</span></span><br><span class="line">        <span class="comment">// 判断是否是OP_CONNECT事件。</span></span><br><span class="line">        <span class="keyword">if</span> ((readyOps &amp; SelectionKey.OP_CONNECT) != <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="comment">// remove OP_CONNECT as otherwise Selector.select(..) will always return without blocking</span></span><br><span class="line">            <span class="comment">// See https://github.com/netty/netty/issues/924</span></span><br><span class="line">            <span class="keyword">int</span> ops = k.interestOps();</span><br><span class="line">            ops &amp;= ~SelectionKey.OP_CONNECT;</span><br><span class="line">            k.interestOps(ops);</span><br><span class="line"></span><br><span class="line">            unsafe.finishConnect();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Process OP_WRITE first as we may be able to write some queued buffers and so free memory.</span></span><br><span class="line">        <span class="comment">// 判断是否是OP_WRITE事件。</span></span><br><span class="line">        <span class="keyword">if</span> ((readyOps &amp; SelectionKey.OP_WRITE) != <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="comment">// Call forceFlush which will also take care of clear the OP_WRITE once there is nothing left to write</span></span><br><span class="line">            ch.unsafe().forceFlush();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Also check for readOps of 0 to workaround possible JDK bug which may otherwise lead</span></span><br><span class="line">        <span class="comment">// to a spin loop</span></span><br><span class="line">        <span class="comment">// 判断是否是OP_READ或者OP_ACCEPT事件。</span></span><br><span class="line">        <span class="keyword">if</span> ((readyOps &amp; (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != <span class="number">0</span> || readyOps == <span class="number">0</span>) &#123;</span><br><span class="line">            unsafe.read();</span><br><span class="line">            <span class="keyword">if</span> (!ch.isOpen()) &#123;</span><br><span class="line">                <span class="comment">// Connection already closed - no need to handle write.</span></span><br><span class="line">                <span class="keyword">return</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (CancelledKeyException ignored) &#123;</span><br><span class="line">        unsafe.close(unsafe.voidPromise());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>到这里我们就看到了netty是如何进行IO检测的，我们梳理一下流程。首先我们通过select进行获取IO事件，当然这里并不只有IO事件，还有外部线程的打断、任务队列和定时任务队列中存在任务，都会打断select的轮询，然后我们遍历这个selector上的SelectionKey，判断当前的IO事件，这样我就检测到了IO事件。并且通过pipeline进行处理这些IO事件。</p>
<h2 id="runTask-执行队列任务"><a href="#runTask-执行队列任务" class="headerlink" title="runTask 执行队列任务"></a>runTask 执行队列任务</h2><p>这里就到了我们NioEventLoop的最后一个流程：执行任务队列和定时任务队列里面的任务。我们先梳理一下大概的流程。在NioEventLoop中提供了接口，用于添加和删除普通任务和定时任务，然后将定时任务与普通任务进行合并，然后进行执行俩种任务。</p>
<h3 id="任务的添加"><a href="#任务的添加" class="headerlink" title="任务的添加"></a>任务的添加</h3><p>我们上面说了，任务分为俩种，一种是普通任务，一种是定时任务，其实我们分析过了，在NioEventLoop构造函数中，就创建了taskQueue。我们一起来看看吧：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// NioEventLoop.java</span></span><br><span class="line">NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider,</span><br><span class="line">             SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler) &#123;</span><br><span class="line">    <span class="comment">// 进入父类构造方法中。</span></span><br><span class="line">    <span class="keyword">super</span>(parent, executor, <span class="keyword">false</span>, DEFAULT_MAX_PENDING_TASKS, rejectedExecutionHandler);</span><br><span class="line">    <span class="keyword">if</span> (selectorProvider == <span class="keyword">null</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> NullPointerException(<span class="string">&quot;selectorProvider&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (strategy == <span class="keyword">null</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> NullPointerException(<span class="string">&quot;selectStrategy&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    provider = selectorProvider;</span><br><span class="line">    selector = openSelector();</span><br><span class="line">    selectStrategy = strategy;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// SingleThreadEventLoop.java</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="title">SingleThreadEventLoop</span><span class="params">(EventLoopGroup parent, Executor executor,</span></span></span><br><span class="line"><span class="params"><span class="function">                                <span class="keyword">boolean</span> addTaskWakesUp, <span class="keyword">int</span> maxPendingTasks,</span></span></span><br><span class="line"><span class="params"><span class="function">                                RejectedExecutionHandler rejectedExecutionHandler)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 继续跟进去</span></span><br><span class="line">    <span class="keyword">super</span>(parent, executor, addTaskWakesUp, maxPendingTasks, rejectedExecutionHandler);</span><br><span class="line">    tailTasks = newTaskQueue(maxPendingTasks);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//SingleThreadEventExecutor.java</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="title">SingleThreadEventExecutor</span><span class="params">(EventExecutorGroup parent, Executor executor,</span></span></span><br><span class="line"><span class="params"><span class="function">                                    <span class="keyword">boolean</span> addTaskWakesUp, <span class="keyword">int</span> maxPendingTasks,</span></span></span><br><span class="line"><span class="params"><span class="function">                                    RejectedExecutionHandler rejectedHandler)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">super</span>(parent);</span><br><span class="line">    <span class="keyword">this</span>.addTaskWakesUp = addTaskWakesUp;</span><br><span class="line">    <span class="keyword">this</span>.maxPendingTasks = Math.max(<span class="number">16</span>, maxPendingTasks);</span><br><span class="line">    <span class="keyword">this</span>.executor = ObjectUtil.checkNotNull(executor, <span class="string">&quot;executor&quot;</span>);</span><br><span class="line">    <span class="comment">// 这里调用PlatformDependent.newMpscQueue(maxPendingTasks);进行创建一个MpscQueue,这里我们之前讲解过了。</span></span><br><span class="line">    taskQueue = newTaskQueue(<span class="keyword">this</span>.maxPendingTasks);</span><br><span class="line">    rejectedExecutionHandler = ObjectUtil.checkNotNull(rejectedHandler, <span class="string">&quot;rejectedHandler&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 然后在外部线程调用NioEventLoop的execute方法时：</span></span><br><span class="line"><span class="comment">//SingleThreadEventExecutor.java</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">execute</span><span class="params">(Runnable task)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (task == <span class="keyword">null</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> NullPointerException(<span class="string">&quot;task&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">boolean</span> inEventLoop = inEventLoop();</span><br><span class="line">    <span class="keyword">if</span> (inEventLoop) &#123;</span><br><span class="line">        addTask(task);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">//这里startThread我们之前就分析过了。</span></span><br><span class="line">        startThread();</span><br><span class="line">        <span class="comment">// 我们仔细来查看这个addTask(task)</span></span><br><span class="line">        addTask(task);</span><br><span class="line">        <span class="keyword">if</span> (isShutdown() &amp;&amp; removeTask(task)) &#123;</span><br><span class="line">            reject();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (!addTaskWakesUp &amp;&amp; wakesUpForTask(task)) &#123;</span><br><span class="line">        wakeup(inEventLoop);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//SingleThreadEventExecutor.java</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">addTask</span><span class="params">(Runnable task)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (task == <span class="keyword">null</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> NullPointerException(<span class="string">&quot;task&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//这里调用了offerTask，那么其实就是往taskQueue中添加一个任务。这里就是对普通任务的添加。</span></span><br><span class="line">    <span class="keyword">if</span> (!offerTask(task)) &#123;</span><br><span class="line">        reject(task);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">offerTask</span><span class="params">(Runnable task)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (isShutdown()) &#123;</span><br><span class="line">        reject();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> taskQueue.offer(task);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>分析了普通任务的添加，我们来查看定时任务的添加，如果你是用过netty的定时任务，那么你应该知道，这个入口就在NioEventLoop的<code>schedule();</code>方法，这个方法在<code>AbstractScheduledEventExecutor</code>类中，它是NioEventLoop的一个父类。 我们来看看这个方法的实现：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// AbstractScheduledEventExecutor.java</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span>  ScheduledFuture&lt;?&gt; schedule(Runnable command, <span class="keyword">long</span> delay, TimeUnit unit) &#123;</span><br><span class="line">    ObjectUtil.checkNotNull(command, <span class="string">&quot;command&quot;</span>);</span><br><span class="line">    ObjectUtil.checkNotNull(unit, <span class="string">&quot;unit&quot;</span>);</span><br><span class="line">    <span class="keyword">if</span> (delay &lt; <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException(</span><br><span class="line">            String.format(<span class="string">&quot;delay: %d (expected: &gt;= 0)&quot;</span>, delay));</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 这里将callable 封装成netty自己的一个FutureTask。</span></span><br><span class="line">    <span class="keyword">return</span> schedule(<span class="keyword">new</span> ScheduledFutureTask&lt;Void&gt;(</span><br><span class="line">        <span class="keyword">this</span>, command, <span class="keyword">null</span>, ScheduledFutureTask.deadlineNanos(unit.toNanos(delay))));</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 这里就是讲定时任务添加到定时任务队列中。</span></span><br><span class="line">&lt;V&gt; <span class="function">ScheduledFuture&lt;V&gt; <span class="title">schedule</span><span class="params">(<span class="keyword">final</span> ScheduledFutureTask&lt;V&gt; task)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 首先判断调用这个schedule方法的线程是不是一个NioEventLoop线程。</span></span><br><span class="line">    <span class="comment">// 如果是的话，那么就直接讲task添加到定时任务队列中，否则就把添加定时任务也当做一个普通任务来进行，</span></span><br><span class="line">    <span class="comment">// 因为这个定时任务队列它并不是线程安全的。因为NioEventLoop与线程是一对一的关系，</span></span><br><span class="line">    <span class="comment">// 所以放在EventLoop的线程中执行是一个单线程操作，这样就不会有线程安全问题。不仅仅是添加，所有关于</span></span><br><span class="line">    <span class="comment">// 定时任务队列相关的操作，都会放到NioEventLoop中去执行，用来保证线程安全。</span></span><br><span class="line">    <span class="keyword">if</span> (inEventLoop()) &#123;</span><br><span class="line">        scheduledTaskQueue().add(task);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        execute(<span class="keyword">new</span> Runnable() &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">                scheduledTaskQueue().add(task);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> task;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们上面分析了，定时任务的添加这一步骤，需要保证在NioEventLoop的线程中进行，为了保证其线程安全。如果不是在，那么把这个操作当成一个普通任务的形式，通过execute方法定时任务添加到定时任务队列中。那么到这里，我们分析了俩种定时任务的添加，下面我们来分析netty怎么去把这俩种任务聚合在一起。</p>
<h3 id="俩种任务的聚合与执行"><a href="#俩种任务的聚合与执行" class="headerlink" title="俩种任务的聚合与执行"></a>俩种任务的聚合与执行</h3><p>还记得netty在执行处理IO事件前，记录了当前时间戳，我们一起来看看之前的代码吧：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// NioEventLoop.java 中的run方法：</span></span><br><span class="line">cancelledKeys = <span class="number">0</span>;</span><br><span class="line">needsToSelectAgain = <span class="keyword">false</span>;</span><br><span class="line"><span class="comment">//这个默认是50</span></span><br><span class="line"><span class="keyword">final</span> <span class="keyword">int</span> ioRatio = <span class="keyword">this</span>.ioRatio;</span><br><span class="line"><span class="keyword">if</span> (ioRatio == <span class="number">100</span>) &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        processSelectedKeys();</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        <span class="comment">// Ensure we always run tasks.</span></span><br><span class="line">        runAllTasks();</span><br><span class="line">    &#125;</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="comment">// 这里记录了一个开始时间</span></span><br><span class="line">    <span class="keyword">final</span> <span class="keyword">long</span> ioStartTime = System.nanoTime();</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">//执行处理IO事件</span></span><br><span class="line">        processSelectedKeys();</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        <span class="comment">// Ensure we always run tasks.</span></span><br><span class="line">        <span class="comment">// 获取处理IO事件花费的时间。</span></span><br><span class="line">        <span class="keyword">final</span> <span class="keyword">long</span> ioTime = System.nanoTime() - ioStartTime;</span><br><span class="line">        <span class="comment">// 这里表示，执行任务的时间不能超过 ioTime * (100 - ioRatio) / ioRatio </span></span><br><span class="line">        runAllTasks(ioTime * (<span class="number">100</span> - ioRatio) / ioRatio);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// SingleThreadEventExecutor.java</span></span><br><span class="line"><span class="comment">// 任务的聚合。</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">boolean</span> <span class="title">fetchFromScheduledTaskQueue</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 这里从定时任务队列中获取第一个任务。这个定时任务列队是根据优先级来的，</span></span><br><span class="line">    <span class="comment">// 因为这个定时任务队列，要么就是可以传入元素对应的比较器，要么就是这个元素本身实现了比较器。</span></span><br><span class="line">    <span class="comment">// 你可以去查看一下这个队列源码，其实很简单。比较规则在ScheduledFutureTask中的compareTo方法，</span></span><br><span class="line">    <span class="comment">// 具体的比较规则就是先比较截止时间，截止时间小的的在前面，</span></span><br><span class="line">    <span class="comment">// 截止时间小的在后面，如果截止时间相同，那么就比较id，id小的在前面，id大的在后面，如果相同，就抛异常。</span></span><br><span class="line">    <span class="keyword">long</span> nanoTime = AbstractScheduledEventExecutor.nanoTime();</span><br><span class="line">    Runnable scheduledTask  = pollScheduledTask(nanoTime);</span><br><span class="line">    <span class="comment">// 这里就比较好理解了，循环取出定时任务，往taskQueue中添加。</span></span><br><span class="line">    <span class="keyword">while</span> (scheduledTask != <span class="keyword">null</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (!taskQueue.offer(scheduledTask)) &#123;</span><br><span class="line">            <span class="comment">// No space left in the task queue add it back to the scheduledTaskQueue so we pick it up again.</span></span><br><span class="line">            scheduledTaskQueue().add((ScheduledFutureTask&lt;?&gt;) scheduledTask);</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        scheduledTask  = pollScheduledTask(nanoTime);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// AbstractEventExecutor.java</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">safeExecute</span><span class="params">(Runnable task)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">// 这里直接去执行run方法。出现异常也并没有做过多处理，只是将异常信息打印出来。</span></span><br><span class="line">        <span class="comment">// 异常内部消化了，所以即使某个任务出现了异常，也会保证后续任务不会受到干扰，</span></span><br><span class="line">        <span class="comment">// 能正常的执行下去。</span></span><br><span class="line">        task.run();</span><br><span class="line">    &#125; <span class="keyword">catch</span> (Throwable t) &#123;</span><br><span class="line">        logger.warn(<span class="string">&quot;A task raised an exception. Task: &#123;&#125;&quot;</span>, task, t);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//SingleThreadEventExecutor.java</span></span><br><span class="line"> <span class="function"><span class="keyword">protected</span> <span class="keyword">boolean</span> <span class="title">runAllTasks</span><span class="params">(<span class="keyword">long</span> timeoutNanos)</span> </span>&#123;</span><br><span class="line">     <span class="comment">// 这里就是任务的聚合</span></span><br><span class="line">     fetchFromScheduledTaskQueue();</span><br><span class="line">     Runnable task = pollTask();</span><br><span class="line">     <span class="keyword">if</span> (task == <span class="keyword">null</span>) &#123;</span><br><span class="line">         afterRunningAllTasks();</span><br><span class="line">         <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">     &#125;</span><br><span class="line">     <span class="comment">// 计算出超时时间</span></span><br><span class="line">     <span class="keyword">final</span> <span class="keyword">long</span> deadline = ScheduledFutureTask.nanoTime() + timeoutNanos;</span><br><span class="line">     <span class="keyword">long</span> runTasks = <span class="number">0</span>;</span><br><span class="line">     <span class="keyword">long</span> lastExecutionTime;</span><br><span class="line">     <span class="comment">// 然后循环去执行任务。</span></span><br><span class="line">     <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">         safeExecute(task);</span><br><span class="line">         runTasks ++;</span><br><span class="line">         <span class="comment">// Check timeout every 64 tasks because nanoTime() is relatively expensive.</span></span><br><span class="line">         <span class="comment">// <span class="doctag">XXX:</span> Hard-coded value - will make it configurable if it is really a problem.</span></span><br><span class="line">         <span class="comment">// 当任务执行到64次的时候，就去判断是否超时，这里为啥要等64次呢，上面也解释了，</span></span><br><span class="line">         <span class="comment">// 因为这个nanoTime他也是一个耗时操作，如果每次执行完都检测，那么将影响效率。</span></span><br><span class="line">         <span class="comment">// 其实这里的超时时间并不保证执行时间必须要小于这个超时时间，只是尽可能的保证不超过超市时间。</span></span><br><span class="line">         <span class="keyword">if</span> ((runTasks &amp; <span class="number">0x3F</span>) == <span class="number">0</span>) &#123;</span><br><span class="line">             lastExecutionTime = ScheduledFutureTask.nanoTime();</span><br><span class="line">             <span class="keyword">if</span> (lastExecutionTime &gt;= deadline) &#123;</span><br><span class="line">                 <span class="keyword">break</span>;</span><br><span class="line">             &#125;</span><br><span class="line">         &#125;</span><br><span class="line">         <span class="comment">// 重新从任务队列中拿到这个任务。</span></span><br><span class="line">         task = pollTask();</span><br><span class="line">         <span class="comment">// 如果队列里面没有任务了， 那么就记录当前时间，赋值给最后执行时间变量。</span></span><br><span class="line">         <span class="keyword">if</span> (task == <span class="keyword">null</span>) &#123;</span><br><span class="line">             lastExecutionTime = ScheduledFutureTask.nanoTime();</span><br><span class="line">             <span class="keyword">break</span>;</span><br><span class="line">         &#125;</span><br><span class="line">     &#125;</span><br><span class="line">     <span class="comment">// 然后任务执行后续操作。</span></span><br><span class="line">     afterRunningAllTasks();</span><br><span class="line">     <span class="keyword">this</span>.lastExecutionTime = lastExecutionTime;</span><br><span class="line">     <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>那么到这里，我们就分析了俩种任务的合并与执行。我们来总结一下这个流程netty做了什么事情吧~</p>
<p>首先，在netty调用自己的<code>select()</code>方法后，会去执行<code>runTask();</code>方法，执行任务队列中的任务，这一步又细分了俩个步骤，第一步，是将俩种任务进行合并，遍历定时任务队列，将定时任务添加到普通任务队列中。添加完成后，根据处理IO时间的时间，然后做一个计算，得出他的一个任务队列执行的一个超时时间，也就是在执行这些任务期间，尽可能不能超过这个超时时间，每过64次去检测一次，至于为啥不是每次去检测一次，因为<code>ScheduledFutureTask.nanoTime();</code>这个方法是一个比较耗时操作，所以不应该每次执行完一个任务就去检测。执行完后，会去记录最后的一个截止时间。</p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>到这里我们就分析完了NioEventLoop一些流程，比如从我们的NioEventLoop的创建，到启动，然后从NioEventLoop怎么去检测与处理IO事件、执行任务队列。这一篇文章的知识点很多，也需要我们自己多动手，仔细跟踪一下源码，进行巩固。</p>
<p>那么我们前面的三个问题通过这篇文章就可以进行解答了。</p>
<p>第一个，默认创建的是CPU核数*2的线程个数。在我们调用execute方法时，先去判断线程是否开启，如果没有开启，那么就去创建一个新的线程，如果开启了，那么就将当前的任务添加到任务队列中等待去执行。</p>
<p>第二个问题，netty是如何解决java nio 空轮训的BUG的。首先出现空轮训的bug的原因，就是当前这个selector出现了问题，可能是网络连接发生了故障，比如出现了RST，然后底层就会触发网络中断，从而唤醒阻塞的java线程，也就是java  nio  的阻塞select，但是返回的标识位还是0，跟正常模式一样，这就导致了用户程序无法判断到底是没有IO事件还是因为发生了RST，所以一般我们是select返回值不为0，我们才结束轮训select，否则就继续轮训select，如果发生了RST，那么此时的select并不是一个阻塞的了，他会立马返回，因为它一调用底层的poll 或者epoll方法，就会立马触发网络中断，从而返回，这样就代码就等价于<code>while(true)&#123;&#125;</code> 从而造成了空轮训，导致CPU飙升100%。然后netty的解决办法也很巧妙，因为它对select有时间限制，所以它最多阻塞timeoutMillis，本来要阻塞这么久，但是如果我发现它并没有阻塞这么久，并且这种情况达到了一定次数，那么netty就认为这个连接可能出现了问题，也就是这个selector出现了问题，那么就把这个selector上的东西重新注册到一个新的selector上，这样就可以避免空轮训的出现。</p>
<p>第三个问题，netty就是在执行任务的时候，首先判断是否是在NioEventLoop的内部线程中，如果不是，那么就把任务封装成一个netty自己的task，然后丢到任务队列中，等待select执行结束，就会去执行这个任务队列。</p>
<blockquote>
<p>微信公众号：码农小谭，一个热爱coding、生活、分享、探讨的打工人，如果我的文章对你有帮助，麻烦给个关注吧~<br>公众号分享技术博文、生活百事、欢迎关注~<br>资料获取方式，无任何套路，也不需要解压验证码，如下：<br>需要java相关资料请回复【java】<br>需要数据库相关资料请回复【数据库】<br>需要计算机网络相关资料请回复【计算机网络】<br>需要操作系统相关资料请回复【操作系统】<br>需要算法相关资料请回复【算法】<br>如果资料链接失效，请点击联系作者添加微信，第一时间会进行更新。</p>
</blockquote>
<p><img src="https://gitee.com/zeroable/ima/raw/master/img/EWM_GZH_MNXT.png"></p>

    </div>

    
    
    

      <footer class="post-footer">
          <div class="post-tags">
              <a href="/tags/netty/" rel="tag"># netty</a>
              <a href="/tags/java/" rel="tag"># java</a>
          </div>

        


        
    <div class="post-nav">
      <div class="post-nav-item"></div>
      <div class="post-nav-item">
    <a href="/2021/10/08/netty%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%9001-netty%E6%9C%8D%E5%8A%A1%E7%AB%AF%E5%90%AF%E5%8A%A8%E8%BF%87%E7%A8%8B/" rel="next" title="netty源码解析01-netty服务端启动过程">
      netty源码解析01-netty服务端启动过程 <i class="fa fa-chevron-right"></i>
    </a></div>
    </div>
      </footer>
    
  </article>
  
  
  



          </div>
          
    
  <div class="comments">
    <div id="lv-container" data-id="city" data-uid="MTAyMC81NDY0NS8zMTExNg=="></div>
  </div>
  

<script>
  window.addEventListener('tabs:register', () => {
    let { activeClass } = CONFIG.comments;
    if (CONFIG.comments.storage) {
      activeClass = localStorage.getItem('comments_active') || activeClass;
    }
    if (activeClass) {
      let activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`);
      if (activeTab) {
        activeTab.click();
      }
    }
  });
  if (CONFIG.comments.storage) {
    window.addEventListener('tabs:click', event => {
      if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return;
      let commentClass = event.target.classList[1];
      localStorage.setItem('comments_active', commentClass);
    });
  }
</script>

        </div>
          
  
  <div class="toggle sidebar-toggle">
    <span class="toggle-line toggle-line-first"></span>
    <span class="toggle-line toggle-line-middle"></span>
    <span class="toggle-line toggle-line-last"></span>
  </div>

  <aside class="sidebar">
    <div class="sidebar-inner">

      <ul class="sidebar-nav motion-element">
        <li class="sidebar-nav-toc">
          文章目录
        </li>
        <li class="sidebar-nav-overview">
          站点概览
        </li>
      </ul>

      <!--noindex-->
      <div class="post-toc-wrap sidebar-panel">
          <div class="post-toc motion-element"><ol class="nav"><li class="nav-item nav-level-1"><a class="nav-link" href="#netty-NioEventLoop%E8%AF%A6%E8%A7%A3"><span class="nav-number">1.</span> <span class="nav-text">netty NioEventLoop详解</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%89%8D%E8%A8%80"><span class="nav-number">1.1.</span> <span class="nav-text">前言</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#NioEventLoop-%E5%88%9B%E5%BB%BA"><span class="nav-number">1.2.</span> <span class="nav-text">NioEventLoop 创建</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#Executor"><span class="nav-number">1.2.1.</span> <span class="nav-text">Executor</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#newChild"><span class="nav-number">1.2.2.</span> <span class="nav-text">newChild()</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#newChooser"><span class="nav-number">1.2.3.</span> <span class="nav-text">newChooser()</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#NioEventLoop-%E5%90%AF%E5%8A%A8"><span class="nav-number">1.3.</span> <span class="nav-text">NioEventLoop 启动</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#NioEventLoop-IO-%E4%BA%8B%E4%BB%B6%E7%9A%84%E6%A3%80%E6%B5%8B%E5%92%8C%E5%A4%84%E7%90%86"><span class="nav-number">1.4.</span> <span class="nav-text">NioEventLoop IO 事件的检测和处理</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#runTask-%E6%89%A7%E8%A1%8C%E9%98%9F%E5%88%97%E4%BB%BB%E5%8A%A1"><span class="nav-number">1.5.</span> <span class="nav-text">runTask 执行队列任务</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E4%BB%BB%E5%8A%A1%E7%9A%84%E6%B7%BB%E5%8A%A0"><span class="nav-number">1.5.1.</span> <span class="nav-text">任务的添加</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E4%BF%A9%E7%A7%8D%E4%BB%BB%E5%8A%A1%E7%9A%84%E8%81%9A%E5%90%88%E4%B8%8E%E6%89%A7%E8%A1%8C"><span class="nav-number">1.5.2.</span> <span class="nav-text">俩种任务的聚合与执行</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E6%80%BB%E7%BB%93"><span class="nav-number">1.6.</span> <span class="nav-text">总结</span></a></li></ol></li></ol></div>
      </div>
      <!--/noindex-->

      <div class="site-overview-wrap sidebar-panel">
        <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
  <p class="site-author-name" itemprop="name">zeroable</p>
  <div class="site-description" itemprop="description">种一棵树最好的时间是十年前，其次是现在。</div>
</div>
<div class="site-state-wrap motion-element">
  <nav class="site-state">
      <div class="site-state-item site-state-posts">
          <a href="/archives/">
        
          <span class="site-state-item-count">4</span>
          <span class="site-state-item-name">日志</span>
        </a>
      </div>
      <div class="site-state-item site-state-categories">
            <a href="/categories/">
          
        <span class="site-state-item-count">2</span>
        <span class="site-state-item-name">分类</span></a>
      </div>
      <div class="site-state-item site-state-tags">
            <a href="/tags/">
          
        <span class="site-state-item-count">2</span>
        <span class="site-state-item-name">标签</span></a>
      </div>
  </nav>
</div>
  <div class="links-of-author motion-element">
      <span class="links-of-author-item">
        <a href="mailto:2011277648@qq.com" title="E-Mail → mailto:2011277648@qq.com" rel="noopener" target="_blank"><i class="fa fa-envelope fa-fw"></i>E-Mail</a>
      </span>
      <span class="links-of-author-item">
        <a href="https://gitee.com/zeroable/ima/raw/master/img/EWM_GZH_MNXT.png" title="微信公众号 → https:&#x2F;&#x2F;gitee.com&#x2F;zeroable&#x2F;ima&#x2F;raw&#x2F;master&#x2F;img&#x2F;EWM_GZH_MNXT.png" rel="noopener" target="_blank"><i class="fab fa-weixin fa-fw"></i>微信公众号</a>
      </span>
      <span class="links-of-author-item">
        <a href="https://www.zhihu.com/people/zeroable" title="知乎 → https:&#x2F;&#x2F;www.zhihu.com&#x2F;people&#x2F;zeroable" rel="noopener" target="_blank"><i class="fab fa-zhihu fa-fw"></i>知乎</a>
      </span>
      <span class="links-of-author-item">
        <a href="https://blog.csdn.net/qq_36707152" title="csdn → https:&#x2F;&#x2F;blog.csdn.net&#x2F;qq_36707152" rel="noopener" target="_blank"><i class="fab fa-csdn fa-fw"></i>csdn</a>
      </span>
  </div>



      </div>
      <div class="wechat_OA">
        <span>欢迎关注我的公众号</span>
        <br>
          <!-- 这里添加你的二维码图片 -->
        <img src ="https://gitee.com/zeroable/ima/raw/master/img/微信公众号.jpg">
      </div

    </div>
  </aside>
  <div id="sidebar-dimmer"></div>


      </div>
    </main>

    <footer class="footer">
      <div class="footer-inner">
        

        

<div class="copyright">
  
  &copy; 
  <span itemprop="copyrightYear">2021</span>
  <span class="with-love">
    <i class="fa fa-heart"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">zeroable</span>
</div>

        
<div class="busuanzi-count">
  <script async src="https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>
    <span class="post-meta-item" id="busuanzi_container_site_uv" style="display: none;">
      <span class="post-meta-item-icon">
        <i class="fa fa-user"></i>
      </span>
      <span class="site-uv" title="总访客量">
        <span id="busuanzi_value_site_uv"></span>
      </span>
    </span>
    <span class="post-meta-divider">|</span>
    <span class="post-meta-item" id="busuanzi_container_site_pv" style="display: none;">
      <span class="post-meta-item-icon">
        <i class="fa fa-eye"></i>
      </span>
      <span class="site-pv" title="总访问量">
        <span id="busuanzi_value_site_pv"></span>
      </span>
    </span>
</div>








      </div>
    </footer>
  </div>

  
  <script src="/lib/anime.min.js"></script>
  <script src="/lib/velocity/velocity.min.js"></script>
  <script src="/lib/velocity/velocity.ui.min.js"></script>

<script src="/js/utils.js"></script>

<script src="/js/motion.js"></script>


<script src="/js/schemes/pisces.js"></script>


<script src="/js/next-boot.js"></script>




  















  

  

<script>
NexT.utils.loadComments(document.querySelector('#lv-container'), () => {
  window.livereOptions = {
    refer: location.pathname.replace(CONFIG.root, '').replace('index.html', '')
  };
  (function(d, s) {
    var j, e = d.getElementsByTagName(s)[0];
    if (typeof LivereTower === 'function') { return; }
    j = d.createElement(s);
    j.src = 'https://cdn-city.livere.com/js/embed.dist.js';
    j.async = true;
    e.parentNode.insertBefore(j, e);
  })(document, 'script');
});
</script>

</body>
</html>
